# 通过Python编程完成一个银行ATM机模拟系统，具备如下功能:
# (1)登陆验证：用户输入用户名密码登陆，检测用户名是否存在以及用户名密码是否匹配；用户名密码共有三次输入机会，超过三次系统退出。
# (2)菜单界面：登陆成功后显示功能操作界面，输入序号选择对应功能。
# (3)用户注册：用户可以输入用户名和密码创建自己的账号，并输入电话号码等信息，如果用户名存在则让用户重新输入用户名。注册后免费赠送5000元余额。
# (4)账户管理：用户可以随时查看自己的账户余额。用户可以输入其他账户用户名，实现转账功能；转账时用户名必须存在，否则提示异常。用户也可以模拟实现存取款功能。
# (5)用户名和密码以及账户信息等必须永久保存。且基于命令行完成，不需要开发GUI界面。

import sqlite3
import re


# 创建用户类，避免客户直接接触数据库
class User:
    def __init__(self, username: str, password: str, balance: float, mobile: int):
        self.username = username
        self.password = password
        self.balance = balance
        self.mobile = mobile

    def deposit(self, deposit_amount: float):
        print('您要存入得金额为 {} 元'.format(self.balance))
        self.balance += deposit_amount
        print('您的新余额为 {} 元'.format(self.balance))

    def withdraw(self, withdraw_amount: float):
        print('您要取出得金额为 {} 元'.format(self.balance))
        self.balance -= withdraw_amount
        print('您的新余额为 {} 元'.format(self.balance))

    def change_username(self, username: str):
        print('用户名将更改为\"{}\"'.format(self.username))
        self.username = username

    def change_password(self, password: str):
        self.password = password

    def __repr__(self):
        return '<User {} {} {} {}>'.format(self.username, self.password, self.balance, self.mobile)


# 数据库类
class DataBase:
    def __init__(self, dbname: str = ':memory:'):
        self.dbname = dbname
        self.conn = sqlite3.connect(dbname)
        self.c = self.conn.cursor()
        try:
            self.conn.execute("""CREATE TABLE user_info(
                            username TEXT unique, 
                            password TEXT, 
                            balance FLOAT, 
                            mobile INT
                            )""")
        except sqlite3.OperationalError:
            pass

    def __del__(self):
        self.conn.close()

    def __repr__(self):
        return "<{} {}>".format(self.__class__, self.dbname)

    def register(self, user):
        self.c.execute("INSERT INTO user_info VALUES (:username, :password, :balance, :mobile)",
                       {'username': user.username, 'password': user.password, 'balance': user.balance,
                        'mobile': user.mobile})
        self.conn.commit()

    def get_login_info(self, username):
        self.c.execute("SELECT * FROM user_info WHERE username=?", (username,))
        return self.c.fetchone()

    def get_all_mobile(self):
        self.c.execute("SELECT mobile FROM user_info")
        return self.c.fetchall()

    def update_mobile(self, user: User):
        self.c.execute("UPDATE user_info set mobile = :mobile", {'mobile': user.mobile, })
        self.conn.commit()
        print('电话已更新')

    def update_username(self, user: User):
        self.c.execute("UPDATE user_info set username = :username", {'username': user.username, })
        self.conn.commit()
        print('用户名已更新')

    def update_password(self, user: User):
        self.c.execute("UPDATE user_info set password = :new_password", {'new_password': user.password, })
        self.conn.commit()
        print('密码已更新')

    def update_balance(self, user: User):
        self.c.execute("UPDATE user_info set balance = :new_balance", {'new_balance': user.balance, })
        self.conn.commit()
        print('您得余额已更新')

    def transfer_money(self, user_from, user_to):
        self.c.execute("UPDATE user_info set balance = :new_balance", {'new_balance': user_from.balance, })
        self.c.execute("UPDATE user_info set balance = :new_balance", {'new_balance': user_to.balance, })
        self.conn.commit()
        print('转账已完成')

    def print_all(self):
        self.c.execute("SELECT * FROM user_info")
        return self.c.fetchall()


# ATM 作为类存在
class ATM:
    _username_pattern = r'[A-Za-z][A-Za-z\d]{4,15}'  # 字母开头，只能包含字母数字，5-15位
    _password_pattern = r'[\S]{6,}'  # 6位以上非空白字符
    _mobile_pattern = r'\d{11,11}'
    _suffix = '.db'

    def __init__(self, name: str = None):
        if name is not None:
            self.name = name + self.__class__._suffix
            self.database = DataBase(self.name)
        else:
            self.name = 'anonymous user'
            self.database = DataBase()

    def main_menu(self):
        welcome_msg = '欢迎使用{}银行, 请输入号码进行选择:\n1.登录\n2.注册\n3.退出'.format(self.name)
        print(welcome_msg)

    def login_menu(self, user: User):
        # 1 2 3 4 功能可以考虑抽象出来
        print('尊敬得用户{}，您好！'.format(user.username))
        while True:
            print('请输入号码进行选择:\n1.查看余额\n2.存款\n3.取款\n4.转账\n5.修改账户信息\n6.退出')
            user_input = input('>>>')
            if user_input == '1':
                print('您的余额为{}元'.format(user.balance))
            elif user_input == '2':
                while True:
                    try:
                        print('请输入要存入的金额:')
                        deposit = float(input('>>>'))
                        if deposit < 0:
                            print('输入金额必须大于0')
                            continue
                        user.balance += deposit
                        print('存入{}元，您的新余额为{}元'.format(deposit, user.balance))
                        self.database.update_balance(user)
                        break
                    except ValueError:
                        print('输入错误，请重新输入')
            elif user_input == '3':
                while True:
                    try:
                        print('请输入要取出的金额:')
                        withdraw = float(input('>>>'))
                        if withdraw < 0:
                            print('输入金额必须大于0')
                            continue
                        user.balance -= withdraw
                        print('取出{}元，您的新余额为{}元'.format(withdraw, user.balance))
                        self.database.update_balance(user)
                        break
                    except ValueError:
                        print('输入错误，请重新输入')
            elif user_input == '4':
                while True:
                    print('请输入要转账的用户：')
                    receiver = input('>>>')
                    if receiver == 'esc':
                        break
                    elif receiver == user.username:
                        print('无法转账给自己,请重新输入')
                        continue
                    receiver_info = self._get_login_info(receiver)
                    if receiver_info:
                        _receiver = User(*receiver_info)
                        while True:
                            print('请输入要转账金额:')
                            try:
                                transfer_amount = float(input('>>>'))
                                if transfer_amount < 0:
                                    print('输入金额必须大于0')
                                    continue
                                print('您将转账{}元给用户{}，请输入1确认，2，取消。'.format(transfer_amount, _receiver.username))
                                confirm = input('>>>')
                                if confirm == '1':
                                    user.balance -= transfer_amount
                                    _receiver.balance += transfer_amount
                                    self.database.transfer_money(user, _receiver)
                                    break
                                elif confirm == '2':
                                    break
                            except ValueError:
                                print('输入错误，请重新输入。')
                        break

                    else:
                        print('未找到用户，请重新输入，或者输入\'esc\'退出。')
            elif user_input == '5':
                while True:
                    print('请输入号码进行选择:\n1.修改密码\n2.更新电话号码\n3.退出')
                    user_input = input('>>>')
                    if user_input == '1':
                        while True:
                            print('请输入新密码:')
                            new_password = input('>>>')
                            if new_password == 'esc':
                                break
                            if not self.check_password(new_password):
                                print('密码为6位以上空白字符，请重新输入,或输入\'esc\'退出')
                                continue
                            elif new_password == user.password:
                                print('密码为6位以上空白字符,或输入\'esc\'退出')
                                continue
                            else:
                                print('您的密码将修改为\'{}\',请输入1确认，2取消'.format(new_password))
                                while True:
                                    confirm = input('>>>')
                                    if confirm == '1':
                                        user.password = new_password
                                        self.database.update_password(user)
                                        break
                                    elif confirm == '2':
                                        break
                                    else:
                                        print('输入错误，请重新输入')
                                        continue
                                break
                    elif user_input == '2':
                        while True:
                            print('请输入新电话号码:')
                            new_mobile = input('>>>')
                            if new_mobile == 'esc':
                                break
                            if not self.check_mobile(new_mobile):
                                print('新号码不是11位数，请重新输入,或输入\'esc\'退出')
                                continue
                            elif int(new_mobile) == user.mobile:
                                print('新电话号码不能与现在电话号码相同,或输入\'esc\'退出')
                                continue
                            elif (int(new_mobile),) in self.database.get_all_mobile():
                                print('电话号码已存在，请重新输入,或输入\'esc\'退出')
                                continue
                            else:
                                print('您的电话号码将修改为\'{}\',请输入1确认，2取消'.format(new_mobile))
                                confirm = input('>>>')
                                if confirm == '1':
                                    user.mobile = int(new_mobile)
                                    self.database.update_mobile(user)
                                    break
                                if confirm == '2':
                                    break
                    elif user_input == '3':
                        break
                    else:
                        print('输入错误，请重新输入')
            elif user_input == '6':
                return

    def open(self):
        while True:
            self.main_menu()
            cmd = input('>>>')
            if cmd == '1':
                print('你正在登录账户')
                count = 3
                login_info = self.login()
                if login_info == '3':
                    continue
                elif login_info:
                    while True:
                        if count == 0:
                            print('输入错误次数太多，系统将退出')
                            return
                        print('请输入密码')
                        password = input('>>>')
                        if password != login_info[1]:
                            count -= 1
                            print('密码输入不正确，你还有{}次机会。'.format(count))
                        else:
                            break

                print('登入成功')
                # 这里进入登录界面
                exist_user = User(*login_info)
                self.login_menu(exist_user)
            elif cmd == '2':
                print('您正在注册账户')
                while True:
                    print('请输入用户名，用户名为字母开头，只能包含字母数字，5-15位')
                    username = input('>>>')
                    if not self.check_username(username):
                        print('输入错误，用户名为字母开头，只能包含字母数字，5-15位')
                        continue
                    elif self._get_login_info(username):
                        print('用户名已存在')
                        continue
                    else:
                        break
                print('用户名设置成功')

                while True:
                    print('请设置密码，密码为6位以上空白字符')
                    password = input('>>>')
                    if not self.check_password(password):
                        print('输入错误，密码为6位以上空白字符')
                        continue
                    else:
                        break
                print('密码设置成功')

                while True:
                    print('请绑定手机号，仅支持中国大陆手机号码')
                    mobile = input('>>>')
                    if not self.check_mobile(mobile):
                        print('输入错误，请重新输入')
                        continue
                    elif (int(mobile),) in self.database.get_all_mobile():
                        print('电话号码已存在，请重新输入')
                        continue
                    else:
                        break
                print('手机号码设置成功')
                print('注册成功，已赠送5000元余额')

                new_user = User(username, password, 5000, int(mobile))
                self.database.register(new_user)
                print('将自动登录')
                # 这里进入登录界面
                self.login_menu(new_user)
            elif cmd == '3':
                print('已退出账户')
                return
            else:
                print('输入错误，请输入数字1-3')

    def _get_login_info(self, username):
        return self.database.get_login_info(username)

    def login(self):
        while True:
            print('请输入用户名：')
            username = input('>>>')
            if username == '3':
                return 'esc'
            ret = self._get_login_info(username)
            if ret:
                return ret
            else:
                print('用户名不存在，请重新输入，或者输入\'esc\'返回主界面注册。')
                continue

    # 用re模块，规范用户输入
    def check_username(self, username):
        r = re.compile(self._username_pattern)
        valid = r.fullmatch(username)
        if valid:
            return username
        else:
            return

    def check_password(self, password):
        r = re.compile(self._password_pattern)
        valid = r.fullmatch(password)
        if valid:
            return password
        else:
            return

    def check_mobile(self, mobile):
        r = re.compile(self._mobile_pattern)
        valid = r.fullmatch(mobile)
        if valid:
            return mobile
        else:
            return

    # 最后写一个退出函数


a = ATM('yi')
print(a.database.print_all())
print(a.__dict__)
a.open()

# 类都用上了。
#发现sqlite3语法更新时，并没有指定row，最后给每位用户分配ID进行匹配，或者利用电话号码匹配。
#存款取款没有考虑余额不足情况，需要稍微修改。
#我就不改了，下次注意